﻿' ----------------------------------------------------------------------------------
' Microsoft Developer & Platform Evangelism
' 
' Copyright (c) Microsoft Corporation. All rights reserved.
' 
' THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
' EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
' OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ----------------------------------------------------------------------------------
' The example companies, organizations, products, domain names,
' e-mail addresses, logos, people, places, and events depicted
' herein are fictitious.  No association with any real company,
' organization, product, domain name, email address, logo, person,
' places, or events is intended or should be inferred.
' ----------------------------------------------------------------------------------

Imports Microsoft.Phone.Notification
Imports System.Windows.Threading
Imports System.IO.IsolatedStorage
Imports System.ComponentModel
Imports System.Collections.ObjectModel

Namespace Push

    Public NotInheritable Class PushContext
        Implements INotifyPropertyChanged
#Region "Fields"

        Private ReadOnly Settings As IsolatedStorageSettings = IsolatedStorageSettings.ApplicationSettings
        Private Shared _current As PushContext
        Private _isConnected As Boolean

#End Region

#Region "Properties"
        Public Shared ReadOnly Property Current() As PushContext
            Get
                Return _current
            End Get
        End Property

        Private _ChannelName As String
        Public Property ChannelName() As String
            Get
                Return _ChannelName
            End Get
            Private Set(ByVal value As String)
                _ChannelName = value
            End Set
        End Property

        Private _ServiceName As String
        Public Property ServiceName() As String
            Get
                Return _ServiceName
            End Get
            Private Set(ByVal value As String)
                _ServiceName = value
            End Set
        End Property

        Private _AllowedDomains As IList(Of Uri)
        Public Property AllowedDomains() As IList(Of Uri)
            Get
                Return _AllowedDomains
            End Get
            Private Set(ByVal value As IList(Of Uri))
                _AllowedDomains = value
            End Set
        End Property

        Private _NotificationChannel As HttpNotificationChannel
        Public Property NotificationChannel() As HttpNotificationChannel
            Get
                Return _NotificationChannel
            End Get
            Private Set(ByVal value As HttpNotificationChannel)
                _NotificationChannel = value
            End Set
        End Property

        Public Property IsConnected() As Boolean
            Get
                Return _isConnected
            End Get
            Set(ByVal value As Boolean)
                If _isConnected <> value Then
                    _isConnected = value
                    NotifyPropertyChanged("IsConnected")
                End If
            End Set
        End Property

        Public Property IsPushEnabled() As Boolean
            Get
                Return GetOrCreate(Of Boolean)("PushContext.IsPushEnabled", False)
            End Get
            Set(ByVal value As Boolean)
                SetOrCreate("PushContext.IsPushEnabled", value)
            End Set
        End Property

        Private Property Dispatcher() As Dispatcher
#End Region

#Region "Events"
        Public Event ErrorEvent As EventHandler(Of PushContextErrorEventArgs)
        Public Event ChannelPrepared As EventHandler(Of PushContextEventArgs)
        Public Event RawNotification As EventHandler(Of HttpNotificationEventArgs)
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
#End Region

#Region "Ctor"

        Public Sub New(ByVal channelName As String, ByVal serviceName As String, ByVal allowedDomains As IList(Of Uri), ByVal dispatcher As Dispatcher)
            If _current IsNot Nothing Then
                ' throw new InvalidOperationException("There should be no more than one push context.");
                Return
            End If

            Me._ChannelName = channelName
            Me._ServiceName = serviceName
            Me._AllowedDomains = allowedDomains
            Me.Dispatcher = dispatcher

            _current = Me
        End Sub

#End Region

#Region "Public Methods"
        Public Sub Connect(ByVal callback As Action(Of HttpNotificationChannel))
            If IsConnected Then
                callback(NotificationChannel)
            Else
                Try
                    ' First, try to pick up an existing channel.
                    NotificationChannel = HttpNotificationChannel.Find(ChannelName)

                    If NotificationChannel Is Nothing Then
                        CreateChannel(callback)
                    Else
                        PrepareChannel(callback)
                    End If

                    Me.UpdateNotificationBindings()
                    Me.IsConnected = True
                Catch ex As Exception
                    OnError(ex)
                End Try
            End If
        End Sub
        Public Sub Disconnect()
            If Not IsConnected Then
                Return
            End If

            Try
                If NotificationChannel IsNot Nothing Then
                    UnbindFromTileNotifications()
                    UnbindFromToastNotifications()
                    UnsubscribeToNotificationEvents()
                    NotificationChannel.Close()
                End If
            Catch ex As Exception
                OnError(ex)
            Finally
                NotificationChannel = Nothing
                IsConnected = False
            End Try
        End Sub
#End Region

#Region "Privates"
        ''' <summary>
        ''' Create channel, subscribe to channel events and open the channel.
        ''' </summary>
        Private Sub CreateChannel(ByVal prepared As Action(Of HttpNotificationChannel))
            ' Create a new channel.
            NotificationChannel = New HttpNotificationChannel(ChannelName, ServiceName)

            ' Register to UriUpdated event. This occurs when channel successfully opens.
            AddHandler NotificationChannel.ChannelUriUpdated, Sub(s, e) Dispatcher.BeginInvoke(Sub() PrepareChannel(prepared))

            ' Trying to Open the channel.
            NotificationChannel.Open()
        End Sub

        Private Sub SubscribeToNotificationEvents()
            If Me.NotificationChannel IsNot Nothing Then
                AddHandler Me.NotificationChannel.HttpNotificationReceived, AddressOf Me.OnRawNotificationReceived
            End If
        End Sub

        Private Sub UnsubscribeToNotificationEvents()
            If Me.NotificationChannel IsNot Nothing Then
                RemoveHandler Me.NotificationChannel.HttpNotificationReceived, AddressOf Me.OnRawNotificationReceived
            End If
        End Sub

        Private Sub OnRawNotificationReceived(ByVal sender As Object, ByVal args As HttpNotificationEventArgs)
            Dispatcher.BeginInvoke(Sub()
                                       RaiseEvent RawNotification(Me, args)
                                   End Sub)
        End Sub

        Private Sub PrepareChannel(ByVal prepared As Action(Of HttpNotificationChannel))
            Try
                prepared(NotificationChannel)
            Catch ex As Exception
                OnError(ex)
            End Try
        End Sub

        Private Sub OnError(ByVal exception As Exception)
            RaiseEvent ErrorEvent(Me, New PushContextErrorEventArgs(exception))
        End Sub

        Private Sub OnChannelPrepared(ByVal args As PushContextEventArgs)
            RaiseEvent ChannelPrepared(Me, args)
        End Sub

        Private Sub BindToTileNotifications()
            Try
                If NotificationChannel IsNot Nothing AndAlso (Not NotificationChannel.IsShellTileBound) Then
                    Dim listOfAllowedDomains = New Collection(Of Uri)(AllowedDomains)
                    NotificationChannel.BindToShellTile(listOfAllowedDomains)
                End If
            Catch ex As Exception
                OnError(ex)
            End Try
        End Sub

        Private Sub BindToToastNotifications()
            Try
                If NotificationChannel IsNot Nothing AndAlso (Not NotificationChannel.IsShellToastBound) Then
                    NotificationChannel.BindToShellToast()
                End If
            Catch ex As Exception
                OnError(ex)
            End Try
        End Sub

        Private Sub UnbindFromTileNotifications()
            Try
                If NotificationChannel IsNot Nothing AndAlso NotificationChannel.IsShellTileBound Then
                    NotificationChannel.UnbindToShellTile()
                End If
            Catch ex As Exception
                OnError(ex)
            End Try
        End Sub

        Private Sub UnbindFromToastNotifications()
            Try
                If NotificationChannel IsNot Nothing AndAlso NotificationChannel.IsShellToastBound Then
                    NotificationChannel.UnbindToShellToast()
                End If
            Catch ex As Exception
                OnError(ex)
            End Try
        End Sub

        Private Sub UpdateNotificationBindings()
            If IsPushEnabled Then
                BindToTileNotifications()
                BindToToastNotifications()
                SubscribeToNotificationEvents()
            Else
                UnbindFromTileNotifications()
                UnbindFromToastNotifications()
                UnsubscribeToNotificationEvents()
            End If
        End Sub

        Private Function GetOrCreate(Of T)(ByVal key As String, Optional ByVal defaultValue As T = Nothing) As T
            Dim value As T
            If Settings.TryGetValue(key, value) Then
                Return value
            End If

            Return defaultValue
        End Function

        Private Sub SetOrCreate(Of T)(ByVal key As String, ByVal value As T)
            Settings(key) = value
        End Sub

        Private Sub NotifyPropertyChanged(ByVal propertyName As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
        End Sub
#End Region
    End Class
End Namespace